package aggregate

import (
	"errors"
	"fmt"
	"gluenet/modules/manager/server/domain/dependency"
	"gluenet/modules/manager/server/domain/entity"
	"gluenet/modules/manager/server/domain/vo"
	"gluenet/modules/manager/server/infra"
	"gluenet/modules/manager/server/infra/prepare"
	"path/filepath"
)

func init() {
	//fmt.Printf("init config factory\n")
	prepare.RegisterFactory(func() {
		intanceFactory := NewInstaceFactory()
		prepare.InjectDependency(func() *InstanceFactory {
			return intanceFactory
		})
	})
}

type InstanceFactory struct {
	AgentRepo    dependency.AgentRepo
	InstanceRepo dependency.InstanceRepo
	ConfigRepo   dependency.ConfigMetaRepo
	DetailRepo   dependency.DetailRepo
	LinksRepo    dependency.LinksRepo
	GuidCLI      dependency.GuidCLI
	ScheduleCLI  dependency.ScheduleCLI
	InstanceCLI  dependency.InstanceCLI
	MetricCLI    dependency.MetricCLI
	//ConfigDetailRepo dependency.ConfigDetailRepo
}

func NewInstaceFactory() *InstanceFactory {
	factory := &InstanceFactory{}
	prepare.FetchDependency(&factory.AgentRepo)
	prepare.FetchDependency(&factory.InstanceRepo)
	prepare.FetchDependency(&factory.ConfigRepo)
	prepare.FetchDependency(&factory.DetailRepo)
	prepare.FetchDependency(&factory.LinksRepo)
	prepare.FetchDependency(&factory.ScheduleCLI)
	prepare.FetchDependency(&factory.InstanceCLI)
	prepare.FetchDependency(&factory.GuidCLI)
	prepare.FetchDependency(&factory.MetricCLI)
	return factory
}

func (r *InstanceFactory) CreateInstance(req vo.CreateInstanceReq) (*entity.Instance, error) {
	cfg, err := r.ConfigRepo.Get(req.ConfigID, req.UserID)
	if err != nil {
		return nil, fmt.Errorf("get config %v", err)
	}
	cfg.Config.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(cfg.DetailRepoKey)
	if err != nil {
		return nil, fmt.Errorf("get config detail %v", err)
	}

	var ins = &entity.Instance{}

	ins.Platform = cfg.Platform
	ins.Version = cfg.Version
	ins.Describe = cfg.Describe
	ins.Kind = cfg.Kind
	ins.ConfigID = cfg.ID
	ins.UserID = cfg.UserID
	ins.DetailRepoValue = cfg.DetailRepoValue

	switch ins.Platform {
	case entity.PlatformGluenets:
		ins.DetailRepoValue.(map[string]interface{})["platform_service"] = map[string]interface{}{}
	case entity.PlatformKubernetes:
		ins.DetailRepoValue.(map[string]interface{})["platform_service"] = map[string]interface{}{
			"jaeger":  "enable",
			"linkerd": "disable",
		}
	}

	ins.Origin = infra.FromPlatform

	ins.Config = cfg
	ins.Name = req.Name
	ins.Label = req.InstanceLabel

	// register guid
	rOrder := &entity.RegisterOrder{}
	rOrder.NewFormInstance(ins)
	ins.GUID, err = r.GuidCLI.Register(rOrder)
	if err != nil {
		return nil, fmt.Errorf("register guid %v", err)
	}
	// run instance
	sOrder := entity.ScheduleOrder{}
	sOrder.NewSchedule(ins)
	sNewOrder, err := r.ScheduleCLI.Schedule(ins, &sOrder)
	if err != nil {
		return nil, fmt.Errorf("schedule guid %v", err)
	}

	runTime, err := r.InstanceCLI.CreateObject(ins, sNewOrder.AgentGuids)
	if err != nil {
		return nil, fmt.Errorf("create object %v", err)
	}
	ins.RunTimeRepoValue = runTime

	ins.DetailRepoKey = infra.BuildDetailRepoKey("instance", ins.Platform, ins.UserID, ins.Kind, ins.GUID, ins.Name)
	ins.RunTimeRepoKey = infra.BuildRuntimeRepoKey("instance", ins.Platform, ins.UserID, ins.Kind, ins.GUID, ins.Name)
	insNew, err := r.InstanceRepo.Add(ins)
	if err != nil {
		return nil, err
	}

	if err = r.DetailRepo.PutDetailKV(insNew.DetailRepoKey, insNew.DetailRepoValue); err != nil {
		r.InstanceRepo.Del(ins.GUID, ins.UserID)
		r.InstanceCLI.RemoveObject(ins.GUID, ins.RunTimeRepoValue)
		return nil, err
	}

	if err = r.DetailRepo.PutDetailKV(insNew.RunTimeRepoKey, insNew.RunTimeRepoValue); err != nil {
		r.InstanceRepo.Del(ins.GUID, ins.UserID)
		r.InstanceCLI.RemoveObject(ins.GUID, ins.RunTimeRepoValue)
		r.DetailRepo.DelDetailKV(ins.DetailRepoKey)
		return nil, err
	}

	return insNew, nil
}

func (r *InstanceFactory) DeleteImportInstance(guid string, userId string) (*entity.Instance, error) {
	obj, err := r.InstanceRepo.Get(guid, userId)
	if err != nil {
		return nil, err
	}
	obj.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(obj.DetailRepoKey)
	if err != nil {
		return nil, err
	}
	obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey)
	if err != nil {
		return nil, err
	}

	if obj.Origin != infra.FromExternal {
		return nil, fmt.Errorf("must be import application can delete user this api")
	}

	_, err = r.InstanceRepo.Del(guid, userId)
	if err != nil {
		return nil, err
	}
	err = r.DetailRepo.DelDetailKV(obj.DetailRepoKey)
	if err != nil {
		return nil, err
	}
	err = r.DetailRepo.DelDetailKV(obj.RunTimeRepoKey)
	if err != nil {
		return nil, err
	}

	return obj, nil
}

func (r *InstanceFactory) ImportInstance(req *vo.ImportInstanceReq) (*entity.Instance, error) {
	var obj = &entity.Instance{}

	obj.Platform = req.Platform
	obj.GUID = req.Guid
	obj.Name = req.Name
	obj.Version = req.Version
	obj.Describe = req.Describe
	obj.UserID = req.UserID
	obj.ConfigID = ""
	obj.Kind = entity.KindApplication
	obj.Origin = infra.FromExternal

	var status = make(map[string]interface{}, len(req.Containers))
	for c := range req.Containers {
		status[c] = infra.Ready
	}
	obj.DetailRepoValue = map[string]interface{}{}
	obj.RunTimeRepoValue = map[string]interface{}{
		"agents":     req.AgentGuid,
		"containers": req.Containers,
		"state":      status,
		"platform":   req.Platform,
	}

	obj.DetailRepoKey = infra.BuildDetailRepoKey("instance", obj.Platform, obj.UserID, obj.Kind, obj.GUID, obj.Name)
	obj.RunTimeRepoKey = infra.BuildRuntimeRepoKey("instance", obj.Platform, obj.UserID, obj.Kind, obj.GUID, obj.Name)

	insNew, err := r.InstanceRepo.Add(obj)
	if err != nil {
		return nil, err
	}

	if err = r.DetailRepo.PutDetailKV(insNew.DetailRepoKey, insNew.DetailRepoValue); err != nil {
		return nil, err
	}

	if err = r.DetailRepo.PutDetailKV(insNew.RunTimeRepoKey, insNew.RunTimeRepoValue); err != nil {
		return nil, err
	}

	return insNew, nil
}

func (r *InstanceFactory) QueryInstance(query map[string]interface{}) ([]entity.Instance, error) {
	instanes, err := r.InstanceRepo.FindBy(query)
	if err != nil && !errors.Is(err, infra.DataNotFind) {
		return nil, fmt.Errorf("get instance meta %v", err)
	}

	for k, instance := range instanes {
		instanes[k].DetailRepoValue, err = r.DetailRepo.GetByRepoKey(instance.DetailRepoKey)
		if err != nil {
			return nil, fmt.Errorf("get detail %v", err)
		}
		instanes[k].RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(instance.RunTimeRepoKey)
		if err != nil {
			return nil, fmt.Errorf("get runtime %v", err)
		}

		instanes[k].Links, err = r.LinksRepo.FindBy(map[string]interface{}{
			"controller_guid": instance.GUID,
		})
		if err != nil && !errors.Is(err, infra.DataNotFind) {
			return nil, err
		}
		if errors.Is(err, infra.DataNotFind) {
			instanes[k].Links = []entity.Link{}
		}
	}
	return instanes, nil
}

func (r *InstanceFactory) DeleteInstance(guid, userId string) (*entity.Instance, error) {
	obj, err := r.InstanceRepo.Get(guid, userId)
	if err != nil {
		return nil, err
	}
	obj.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(obj.DetailRepoKey)
	if err != nil {
		return nil, err
	}
	obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey)
	if err != nil {
		return nil, err
	}

	if obj.Origin != infra.FromPlatform {
		return nil, fmt.Errorf("this api only can delete instance from platfrom")
	}

	if err = r.InstanceCLI.RemoveObject(guid, obj.RunTimeRepoValue); err != nil {
		return nil, err
	}

	_, err = r.InstanceRepo.Del(guid, userId)
	if err != nil {
		return nil, err
	}
	err = r.DetailRepo.DelDetailKV(obj.DetailRepoKey)
	if err != nil {
		return nil, err
	}
	err = r.DetailRepo.DelDetailKV(obj.RunTimeRepoKey)
	if err != nil {
		return nil, err
	}

	return obj, nil
}

func (r *InstanceFactory) QueryInstanceLinks(guid, userID string) (interface{}, error) {
	links, err := r.LinksRepo.FindBy(map[string]interface{}{
		"controller_guid": guid,
	})
	if err != nil && !errors.Is(err, infra.DataNotFind) {
		return nil, err
	}

	apps, err := r.InstanceRepo.FindBy(map[string]interface{}{
		"kind":    entity.KindApplication,
		"user_id": userID,
	})
	if err != nil {
		return nil, err
	}

	if len(apps) < len(links) {
		return nil, fmt.Errorf("apps count < links")
	}

	linked, i := make([]string, len(links)), 0
	unlinked, j := make([]string, len(apps)-len(links)), 0

	if len(links) == 0 {
		for k, app := range apps {
			unlinked[k] = app.GUID
		}
	} else {
		for _, app := range apps {
			mark := true
			for _, link := range links {
				if app.GUID == link.ObjectGUID {
					mark = false
				}
			}
			if mark {
				unlinked[j] = app.GUID
				j++
			} else {
				linked[i] = app.GUID
				i++
			}
		}
	}

	return map[string]interface{}{
		"linked":   linked,
		"unlinked": unlinked,
	}, nil
}

func (r *InstanceFactory) LinkInstance(cid, oid, userID string) (interface{}, error) {
	_, err := r.InstanceRepo.FindBy(map[string]interface{}{
		"guid":    cid,
		"user_id": userID,
		"kind":    entity.KindStrategy,
	})
	if err != nil {
		return nil, err
	}
	_, err = r.InstanceRepo.FindBy(map[string]interface{}{
		"guid":    oid,
		"user_id": userID,
		"kind":    entity.KindApplication,
	})
	if err != nil {
		return nil, err
	}

	lk := &entity.Link{}
	lk.New(oid, cid)

	lkNew, err := r.LinksRepo.New(lk)
	if err != nil {
		return nil, err
	}
	return lkNew, nil
}

func (r *InstanceFactory) DelAllLink(cid string) error {
	return r.LinksRepo.DelAll(cid)
}

func (r *InstanceFactory) GetDagJaegerAll(userID string) (interface{}, error) {
	objs, err := r.InstanceRepo.FindBy(map[string]interface{}{
		"user_id": userID,
	})
	if err != nil {
		return nil, err
	}

	var dagTotal = make(map[string]interface{})
	for _, obj := range objs {
		if obj.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(obj.DetailRepoKey); err != nil {
			return nil, err
		}
		if obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey); err != nil {
			return nil, err
		}
		dag, err := r.MetricCLI.GetInstanceGraphJaeger(&obj)
		if err != nil {
			return nil, err
		}
		dagTotal[obj.GUID] = dag
	}

	return dagTotal, nil
}

func (r *InstanceFactory) GetDagLinkerdAll(userID string) (interface{}, error) {
	objs, err := r.InstanceRepo.FindBy(map[string]interface{}{
		"user_id": userID,
	})
	if err != nil {
		return nil, err
	}

	var dagTotal = make(map[string]interface{})
	for _, obj := range objs {
		if obj.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(obj.DetailRepoKey); err != nil {
			return nil, err
		}
		if obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey); err != nil {
			return nil, err
		}
		dag, err := r.MetricCLI.GetInstanceGraphLinkerd(&obj)
		if err != nil {
			return nil, err
		}
		dagTotal[obj.GUID] = dag
	}

	return dagTotal, nil
}

func (r *InstanceFactory) GetDagJaeger(guid, userID string) (interface{}, error) {
	obj, err := r.InstanceRepo.Get(guid, userID)
	if err != nil {
		return nil, err
	}

	if obj.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(obj.DetailRepoKey); err != nil {
		return nil, err
	}
	if obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey); err != nil {
		return nil, err
	}

	dag, err := r.MetricCLI.GetInstanceGraphJaeger(obj)
	if err != nil {
		return nil, err
	}

	return dag, nil
}

func (r *InstanceFactory) UpdateDagJaeger(userID string) error {
	objs, err := r.InstanceRepo.FindBy(map[string]interface{}{
		"user_id": userID,
	})
	if err != nil {
		return err
	}

	for _, obj := range objs {
		if obj.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(obj.DetailRepoKey); err != nil {
			return err
		}
		if obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey); err != nil {
			return err
		}

		if err = r.MetricCLI.UpdateInstanceGraphJaeger(&obj); err != nil && !errors.Is(err, infra.DataNotFind) {
			return err
		}
	}

	return nil
}

func (r *InstanceFactory) UpdateDagLinkerd(userID string) error {
	objs, err := r.InstanceRepo.FindBy(map[string]interface{}{
		"user_id": userID,
	})
	if err != nil {
		return err
	}

	for _, obj := range objs {
		if obj.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(obj.DetailRepoKey); err != nil {
			return err
		}
		if obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey); err != nil {
			return err
		}

		if err = r.MetricCLI.UpdateInstanceGraphLinkerd(&obj); err != nil && !errors.Is(err, infra.DataNotFind) {
			return err
		}
	}

	return nil
}

func (r *InstanceFactory) GetInstanceConfig(configId, user string) (*entity.Config, error) {
	return r.ConfigRepo.Get(configId, user)
}

func (r *InstanceFactory) SaveReport(report *vo.InstanceReport) error {
	repoKey := filepath.ToSlash(filepath.Join(infra.ReportRepoKey, report.Guid))

	reportValue, err := r.DetailRepo.GetByRepoKey(repoKey)
	if err != nil && !errors.Is(err, infra.DataNotFind) {
		return err
	}

	if errors.Is(err, infra.DataNotFind) {
		data := map[string]interface{}{
			report.Op: report.Data,
		}
		if err = r.DetailRepo.PutDetailKV(repoKey, data); err != nil {
			return err
		}
	} else {
		value, ok := reportValue.(map[string]interface{})
		if !ok {
			return fmt.Errorf("report value is not report")
		}
		value[report.Op] = report.Data
		if err = r.DetailRepo.PutDetailKV(repoKey, value); err != nil {
			return err
		}
	}
	return nil
}

func (r *InstanceFactory) GetReport(guid string, container string) (interface{}, error) {
	repoKey := filepath.ToSlash(filepath.Join(infra.ReportRepoKey, guid+"_"+container))
	fmt.Printf("get repoKey %v\n\n", repoKey)
	reportValue, err := r.DetailRepo.GetByRepoKey(repoKey)
	if err != nil {
		return nil, err
	}
	return reportValue, nil
}

func (r *InstanceFactory) GetFile(guid string, container string, url string, userId string) (interface{}, error) {
	obj, err := r.InstanceRepo.Get(guid, userId)
	if err != nil {
		return nil, err
	}
	obj.DetailRepoValue, err = r.DetailRepo.GetByRepoKey(obj.DetailRepoKey)
	if err != nil {
		return nil, err
	}
	obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey)
	if err != nil {
		return nil, err
	}

	urlPrefix := obj.DetailRepoValue.(map[string]interface{})["spec"].(map[string]interface{})["services"].(map[string]interface{})[container].(map[string]interface{})["servic_perfix"].(string)
	agentGuid := obj.RunTimeRepoValue.(map[string]interface{})["agents"].(map[string]interface{})[container].(string)

	return r.InstanceCLI.HttpProxy(agentGuid, urlPrefix+url)
}

func (r *InstanceFactory) UpdateInstanceContainers() error {
	objs, err := r.InstanceRepo.FindBy(map[string]interface{}{
		"platform": entity.PlatformKubernetes,
		"origin":   infra.FromPlatform,
	})
	if err != nil {
		return err
	}

	for _, obj := range objs {
		obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey)
		if err != nil {
			return err
		}
		r.MetricCLI.UpdateInstanceContainerMetric(&obj)
		err = r.DetailRepo.PutDetailKV(obj.RunTimeRepoKey, obj.RunTimeRepoValue)
		if err != nil {
			return err
		}
	}
	return nil
}

func (r *InstanceFactory) GetInstanceContainerAgent(guid, container string) (interface{}, error) {
	return nil, nil
}

func (r *InstanceFactory) InfoStrategy(sGuid string, aGuids []string, userID string) (interface{}, error) {
	//st,err := r.InstanceRepo.Get(sGuid,userID)
	//if err !=nil {
	//	return nil, err
	//}
	var best, grant []string
	for _, v := range aGuids {
		obj, err := r.InstanceRepo.Get(v, userID)
		if err != nil {
			return nil, err
		}
		if obj.Label == "label.demo.car" {
			grant = append(grant, obj.GUID)
		}
		if obj.Label == "label.demo.car.inter" {
			best = append(best, obj.GUID)
		}
	}

	if len(best) == 0 || len(grant) == 0 {
		return nil, fmt.Errorf("need inter and car")
	}

	return r.InstanceCLI.InfoStrategy(sGuid, best, grant)
}

func (r *InstanceFactory) JaegerProxy(iGuid string) (interface{}, error) {
	obj, err := r.InstanceRepo.GetByGuid(iGuid)
	if err != nil {
		return nil, err
	}
	obj.RunTimeRepoValue, err = r.DetailRepo.GetByRepoKey(obj.RunTimeRepoKey)
	if err != nil {
		return nil, err
	}

	if obj.Platform != entity.PlatformKubernetes {
		return nil, fmt.Errorf("must be k8s application guid")
	}

	agents := obj.RunTimeRepoValue.(map[string]interface{})["agents"].(string)
	//ags,err := r.AgentRepo.Get(agents)
	//if err !=nil {
	//	return nil, err
	//}

	// todo

	res := map[string]interface{}{
		iGuid: map[string]interface{}{
			"agent": agents,
			"proxy": "test_url",
		},
	}
	return res, err
}
